package com.hqd.ch03.v19.aop.framework;

import com.hqd.ch03.v19.aop.ProxyMethodInvocation;
import com.hqd.ch03.v19.aopalliance.intercept.MethodInterceptor;
import com.hqd.ch03.v19.aopalliance.intercept.MethodInvocation;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 连接点类，用以执行通知及目标方法
 */
public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Cloneable {
    private Object proxy;
    private Object target;
    private Method method;
    private Object[] args;
    private List<? extends MethodInterceptor> mis;
    private Map<String, Object> userAttributes = new HashMap<>();
    private int currentInterceptorIndex = -1;

    public ReflectiveMethodInvocation(Object target, Object proxy, Method method,
                                      Object[] args, List<MethodInterceptor> mis) {
        this.proxy = proxy;
        this.target = target;
        this.method = method;
        this.args = args;
        this.mis = mis;
    }

    @Nonnull
    @Override
    public Method getMethod() {
        return method;
    }

    @Nonnull
    @Override
    public Object[] getArguments() {
        return args;
    }

    @Override
    public void setArguments(Object... arguments) {
        this.args = arguments;
    }

    @Override
    public MethodInvocation invocableClone() {
        Object[] args = this.args.clone();
        return invocableClone(args);
    }

    @Override
    public MethodInvocation invocableClone(Object... arguments) {
        try {
            ReflectiveMethodInvocation clone = (ReflectiveMethodInvocation) clone();
            clone.args = arguments;
            return clone;
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void setUserAttribute(String key, @Nullable Object value) {
        this.userAttributes.put(key, value);
    }

    @Nullable
    @Override
    public Object getUserAttribute(String key) {
        return this.userAttributes.get(key);
    }

    @Nullable
    @Override
    public Object proceed() throws Throwable {
        /**
         * 链式调用通知
         */
        if (mis.size() - 1 == currentInterceptorIndex) {
            return getMethod().invoke(target, args);
        }
        MethodInterceptor methodInterceptor = this.mis.get(++currentInterceptorIndex);

        return methodInterceptor.invoke(this);
    }

    @Nullable
    @Override
    public Object getThis() {
        return target;
    }

    @Nonnull
    @Override
    public AccessibleObject getStaticPart() {
        return method;
    }

    @Override
    public Object getProxy() {
        return proxy;
    }


    public Map<String, Object> getUserAttributes() {
        return userAttributes;
    }
}
